#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

#include <psp2/kernel/processmgr.h>
#include <psp2/system_param.h>
#include <psp2/shellutil.h>
#include <psp2/display.h>
#include <psp2/rtc.h>
#include <psp2/io/fcntl.h>
#include <psp2/gxm.h>

#include "utils.h"
#include "config.h"
#include "init.h"
#include "browser.h"
#include "ui.h"

static int home_locked = 0, suspend_locked = 0, oled_off_locked = 0, oled_dimming_locked = 0;
static int usb_connection_locked = 0;
static int quick_menu_locked = 0;

static int debug_x = 40, debug_y = 20;

CtrlPad ctrl_pad;
TouchPad touch_pad;

void resetDebugScreenXY()
{
    debug_x = 40;
    debug_y = 20;
}

void debugScreenPrintf(char *text, ...)
{
    va_list list;
    char string[512];

    va_start(list, text);

    vsprintf(string, text, list);
    va_end(list);

    UiDrawText(debug_x, debug_y, GREEN, string);
    debug_y += 20;
}

int APP_LOG(char *text, ...)
{
#ifdef DEBUG
    va_list list;
    char string[512];

    va_start(list, text);

    vsprintf(string, text, list);
    va_end(list);

    printf(string);
#endif

    return 0;
}

int isValidLocation(int x, int y, int sx, int sy, int dx, int dy)
{
    if (x < sx || x > dx)
        return 0;
    if (y < sy || y > dy)
        return 0;
    return 1;
}

int hasEndSlash(const char *path)
{
    return path[strlen(path) - 1] == '/';
}

int removeEndSlash(char *path)
{
    int len = strlen(path);

    if (path[len - 1] == '/')
    {
        path[len - 1] = '\0';
        return 1;
    }

    return 0;
}

int addEndSlash(char *path)
{
    int len = strlen(path);
    if (len < MAX_PATH_LENGTH - 2)
    {
        if (path[len - 1] != '/')
        {
            path[len] = '/';
            path[len + 1] = '\0';
            return 1;
        }
    }

    return 0;
}

void convertUtcToLocalTime(SceDateTime *time_local, SceDateTime *time_utc)
{
    SceRtcTick tick;
    sceRtcGetTick(time_utc, &tick);
    sceRtcConvertUtcToLocalTime(&tick, &tick);
    sceRtcSetTick(time_local, &tick);
}

void convertLocalTimeToUtc(SceDateTime *time_utc, SceDateTime *time_local)
{
    SceRtcTick tick;
    sceRtcGetTick(time_local, &tick);
    sceRtcConvertLocalTimeToUtc(&tick, &tick);
    sceRtcSetTick(time_utc, &tick);
}

void getSizeString(char string[16], uint64_t size)
{
    double double_size = (double)size;

    int i = 0;
    static char *units[] = {"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"};
    while (double_size >= 1024.0)
    {
        double_size /= 1024.0;
        i++;
    }

    snprintf(string, 16, "%.*f %s", (i == 0) ? 0 : 2, double_size, units[i]);
}

void getDateString(char string[24], int date_format, SceDateTime *time)
{
    SceDateTime time_local;
    convertUtcToLocalTime(&time_local, time);

    switch (date_format)
    {
    case SCE_SYSTEM_PARAM_DATE_FORMAT_YYYYMMDD:
        snprintf(string, 24, "%04d/%02d/%02d", time_local.year, time_local.month, time_local.day);
        break;

    case SCE_SYSTEM_PARAM_DATE_FORMAT_DDMMYYYY:
        snprintf(string, 24, "%02d/%02d/%04d", time_local.day, time_local.month, time_local.year);
        break;

    case SCE_SYSTEM_PARAM_DATE_FORMAT_MMDDYYYY:
        snprintf(string, 24, "%02d/%02d/%04d", time_local.month, time_local.day, time_local.year);
        break;
    }
}

void getTimeString(char string[16], int time_format, SceDateTime *time)
{
    SceDateTime time_local;
    convertUtcToLocalTime(&time_local, time);

    switch (time_format)
    {
    case SCE_SYSTEM_PARAM_TIME_FORMAT_12HR:
        snprintf(string, 16, "%02d:%02d %s", (time_local.hour > 12) ? (time_local.hour - 12) : ((time_local.hour == 0) ? 12 : time_local.hour),
                 time_local.minute, time_local.hour >= 12 ? "PM" : "AM");
        break;

    case SCE_SYSTEM_PARAM_TIME_FORMAT_24HR:
        snprintf(string, 16, "%02d:%02d", time_local.hour, time_local.minute);
        break;
    }
}

void getDurationString(char string[16], uint64_t ms)
{
    static uint64_t second_cnv = 1000;
    static uint64_t minute_cnv = 60 * 1000;
    static uint64_t hour_cnv = 60 * 60 * 1000;

    int hour = 0, minute = 0, second = 0;

    uint64_t remaining = ms;

    if (remaining > hour_cnv)
    {
        hour = remaining / hour_cnv;
        remaining -= hour * hour_cnv;
    }
    if (remaining > minute_cnv)
    {
        minute = remaining / minute_cnv;
        remaining -= minute * minute_cnv;
    }
    if (remaining > second_cnv)
    {
        second = remaining / second_cnv;
        remaining -= second * second_cnv;
    }

    if (hour > 0)
        snprintf(string, 16, "%02d:%02d:%02d", hour, minute, second);
    else
        snprintf(string, 16, "%02d:%02d", minute, second);
}

void refreshListPos(int *top_pos, int *focus_pos, int length, int lines)
{
    int temp_top_pos = *top_pos;
    int temp_focus_pos = *focus_pos;

    if (temp_focus_pos > length - 1)
    {
        temp_focus_pos = length - 1;
    }
    if (temp_focus_pos < 0)
    {
        temp_focus_pos = 0;
    }

    int lines_center = (int)((float)lines / 2 + 0.5f);
    temp_top_pos = temp_focus_pos - lines_center + 1;
    if (temp_top_pos > length - lines)
    {
        temp_top_pos = length - lines;
    }
    if (temp_top_pos < 0)
    {
        temp_top_pos = 0;
    }

    *top_pos = temp_top_pos;
    *focus_pos = temp_focus_pos;
}

void refreshListPosNoFocus(int *top_pos, int length, int lines)
{
    int temp_top_pos = *top_pos;

    if (temp_top_pos > length - lines)
    {
        temp_top_pos = length - lines;
    }
    if (temp_top_pos < 0)
    {
        temp_top_pos = 0;
    }

    *top_pos = temp_top_pos;
}

void moveRefreshListPos(int type, int *top_pos, int *focus_pos, int length, int lines)
{
    int temp_top_pos = *top_pos;
    int temp_focus_pos = *focus_pos;
    if (type == MOVE_TYPE_PREV)
        temp_focus_pos--;
    else if (type == MOVE_TYPE_NEXT)
        temp_focus_pos++;
    if (type == MOVE_TYPE_PREV_PAGE)
        temp_focus_pos -= lines;
    else if (type == MOVE_TYPE_NEXT_PAGE)
        temp_focus_pos += lines;

    refreshListPos(&temp_top_pos, &temp_focus_pos, length, lines);
    *top_pos = temp_top_pos;
    *focus_pos = temp_focus_pos;
}

void moveRefreshListPosNoFocus(int type, int *top_pos, int length, int lines)
{
    int temp_top_pos = *top_pos;
    if (type == MOVE_TYPE_PREV)
        temp_top_pos--;
    else if (type == MOVE_TYPE_NEXT)
        temp_top_pos++;
    if (type == MOVE_TYPE_PREV_PAGE)
        temp_top_pos -= lines;
    else if (type == MOVE_TYPE_NEXT_PAGE)
        temp_top_pos += lines;

    refreshListPosNoFocus(&temp_top_pos, length, lines);
    *top_pos = temp_top_pos;
}

void lockHome()
{
    if (!home_locked)
    {
        sceShellUtilLock(SCE_SHELL_UTIL_LOCK_TYPE_PS_BTN);
        home_locked = 1;
    }
}

void unlockHome()
{
    if (home_locked)
    {
        sceShellUtilUnlock(SCE_SHELL_UTIL_LOCK_TYPE_PS_BTN);
        home_locked = 0;
    }
}

void lockUsbConnection()
{
    if (!usb_connection_locked)
    {
        sceShellUtilLock(SCE_SHELL_UTIL_LOCK_TYPE_USB_CONNECTION);
        usb_connection_locked = 1;
    }
}

void unlockUsbConnection()
{
    if (usb_connection_locked)
    {
        sceShellUtilUnlock(SCE_SHELL_UTIL_LOCK_TYPE_USB_CONNECTION);
        usb_connection_locked = 0;
    }
}

void lockQuickMenu()
{
    if (!quick_menu_locked)
    {
        sceShellUtilLock(SCE_SHELL_UTIL_LOCK_TYPE_QUICK_MENU);
        quick_menu_locked = 1;
    }
}

void unlockQuickMenu()
{
    if (quick_menu_locked)
    {
        sceShellUtilUnlock(SCE_SHELL_UTIL_LOCK_TYPE_QUICK_MENU);
        quick_menu_locked = 0;
    }
}

void lockSuspend()
{
    if (suspend_locked < 0)
        suspend_locked = 1;
    else
        suspend_locked++;
}

void unlockSuspend()
{
    if (suspend_locked > 0)
        suspend_locked--;
    else if (suspend_locked < 0)
        suspend_locked = 0;
}

void lockOledOff()
{
    if (oled_off_locked < 0)
        oled_off_locked = 1;
    else
        oled_off_locked++;
}

void unlockOledOff()
{
    if (oled_off_locked > 0)
        oled_off_locked--;
    else if (oled_off_locked < 0)
        oled_off_locked = 0;
}

void lockOledDimming()
{
    if (oled_dimming_locked < 0)
        oled_dimming_locked = 1;
    else
        oled_dimming_locked++;
}

void unlockOledDimming()
{
    if (oled_dimming_locked > 0)
        oled_dimming_locked--;
    else if (oled_dimming_locked < 0)
        oled_dimming_locked = 0;
}

static int power_tick_thread(SceSize args, void *argp)
{
    while (1)
    {
        if (suspend_locked > 0 || oled_off_locked > 0 || oled_dimming_locked > 0)
        {
            int type = SCE_KERNEL_POWER_TICK_DEFAULT;
            if (suspend_locked > 0)
                type |= SCE_KERNEL_POWER_TICK_DISABLE_AUTO_SUSPEND;
            if (oled_off_locked > 0)
                type |= SCE_KERNEL_POWER_TICK_DISABLE_OLED_OFF;
            if (oled_dimming_locked > 0)
                type |= SCE_KERNEL_POWER_TICK_DISABLE_OLED_DIMMING;
            sceKernelPowerTick(type);
        }
        sceKernelDelayThread(10 * 1000 * 1000);
    }

    return 0;
}

void initPowerTickThread()
{
    SceUID thid = sceKernelCreateThread("power_tick_thread", power_tick_thread, 0x10000100, 0x40000, 0, 0, NULL);
    if (thid >= 0)
        sceKernelStartThread(thid, 0, NULL);
}
